﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Reflection;
using Fuzzware.Common;
using Fuzzware.Common.DataSchema.Restrictions;

namespace Fuzzware.ConvertFromXML.Processors
{
    class NodePostProcessor
    {
        /// <summary>
        /// Update the value of all nodes with XML PI
        /// </summary>
        public void Process(OutputNode RootNode)
        {
            // Make a list of nodes with IDs and nodes that need to be processed
            List<OutputNode> NodesWithIds = new List<OutputNode>();
            List<OutputNode> NodesWithProcFns = new List<OutputNode>();
            NodeProcessor.AddNodesToLists(RootNode, NodesWithIds, NodesWithProcFns);

            // Create sorted list of paired nodes
            NodeOrderComparer comparer = new NodeOrderComparer();
            SortedList<OutputNode, OutputNode> NodeProcList = new SortedList<OutputNode, OutputNode>(NodesWithProcFns.Count, comparer);
            for (int i = 0; i < NodesWithProcFns.Count; i++)
            {
                bool FoundNodeId = false;
                String RefId = NodesWithProcFns[i].Processor.RefId;
                for (int j = 0; j < NodesWithIds.Count; j++)
                {
                    // Nodes can have more than one Id, but there is still only the one node
                    for (int k = 0; k < NodesWithIds[j].Processor.NodeId.Count; k++)
                    {
                        if (RefId == NodesWithIds[j].Processor.NodeId[k])
                        {
                            NodeProcList.Add(NodesWithProcFns[i], NodesWithIds[j]);
                            FoundNodeId = true;
                            break;
                        }
                    }
                }
                if (!FoundNodeId)
                    Log.Write(MethodBase.GetCurrentMethod(), "Could not find a node with RefId = '" + RefId + "'", Log.LogType.Warning);
            }

            // Go through list and apply processing functions
            for (int i = 0; i < NodeProcList.Count; i++)
            {
                // A special PI is placed before a node if it is the target of fuzzing (simple types), thus we don't update it.
                if (NodeProcList.Keys[i].TargetNode)
                    continue;
                MemoryStream OutputStream = new MemoryStream();
                NodeProcList.Values[i].Serialise(OutputStream, true, 0, false);
                byte[] rawbytes = OutputStream.ToArray();
                NodeProcList.Keys[i].Processor.ProcessingFn(ref rawbytes, NodeProcList.Values[i], NodeProcList.Keys[i]);

                // Need to validate this new value to ensure it does not violate the XML schema
                TypeRestrictor Restrictor = NodeProcList.Keys[i].Restrictor;
                //if (!Restrictor.Validate(Coder.DecodeToInput(rawbytes, NodeProcList.Keys[i].TypeCode, NodeProcList.Keys[i].OutputAs, NodeProcList.Keys[i].OutputEncoding)))
                String NewValue = NodeProcList.Keys[i].OutputEncoding.GetString(rawbytes);
                if (!Restrictor.Validate(NewValue))
                {
                    throw new ValidateFailedException();
                }

                NodeProcList.Keys[i].ResetWriteElementValue(NewValue);

                // Determine if the updated Node (Key) was a child of the subject Node (Value), if so we need to reset the Element Value
                // of the subject Node (and its descendants), otherwise when we serialise it for real, it will think it has already been 
                // serialised, but in fact contains a possibly incorrect value, since the updated Node was a child of it.  This situation 
                // occurs when the size field of a block of data is in that block of data and is of a fixed size e.g. 2 bytes.
                //if (NodeProcList.Keys[i].HasAncestor(NodeProcList.Values[i]))
                //    NodeProcList.Keys[i].ResetElementValueToAncestor(NodeProcList.Values[i]);

                // We reset the element value of all parents up to root.  Note this will not reset all siblings in the hierarchy so we
                // still benefit from not serialising nodes more than once.  We do this to ensure that any changes made by the processors
                // will appear in the output and not be missed due to previously serialised nodes.
                NodeProcList.Keys[i].ResetElementValueToRoot();
            }

            // Dereference these vars
            NodesWithIds = null;
            NodesWithProcFns = null;
            NodeProcList = null;
        }
    }
}
